<template>
  <div class="cc-form" v-if="!initializing">
    <div
      v-if="tipsTopHtml"
      class="cc-form-tips-top"
      v-html="tipsTopHtml"
    ></div>
    <div
      v-if="formInfo.batchEdit === '1' && formInfo.showModel === '分割线'"
      class="cc-form-model"
    >
      <el-divider
        :border-style="getStyle(formInfo.dividerStyle)"
        :content-position="getPosition(formInfo.dividerPosition)"
      >{{formInfo.modelList[0].name || ''}}</el-divider>
    </div>
    <table-edit
      v-if="formInfo.batchEdit === '1'"
      ref="tableEditRef"
      :compName="name"
      :customFun="customFun"
      :disabled="disabled"
      :required="formInfo.required"
      :autofillList="formInfo.autofillList"
      :formFieldList="formInfo.modelList[0].fieldList"
      :fieldList="fieldList"
      :dataList="formData.list"
      :defaultValue="defaultValue || {}"
      :showOperateButton="formInfo.showOperateButton === '1'"
    />
    <el-form
      v-else
      ref="dataForm"
      v-loading="loading"
      :disabled="disabled"
      :hide-required-asterisk="disabled"
      :model="formData"
      :rules="disabled ? {} : rules"
      :validate-on-rule-change="false"
    >
      <form-auto
        v-if="formInfo.layoutType === '自动'"
        v-bind="formInfo"
        v-bind:operate="operate"
        v-bind:fieldList="fieldList"
      />
      <form-custom
        v-else-if="formInfo.layoutType === '自定义'"
        v-bind="formInfo"
        v-bind:fieldList="fieldList"
      />
    </el-form>
    <div
      v-if="tipsBottomHtml"
      class="cc-form-tips-bottom"
      v-html="tipsBottomHtml"
    ></div>
  </div>
</template>

<script setup>
import { ref, onMounted, provide, inject, watch, nextTick } from 'vue';
import FormAuto from '@/codeless/view/form-auto.vue';
import FormCustom from '@/codeless/view/form-custom.vue';
import TableEdit from "@/codeless/view/table-edit.vue";
import api from "@/http/api";
import { ElMessage } from "element-plus";
import { useUserStore } from "@/pinia/modules/user";
import { getDataForm, getDataFieldList } from '@/utils/codeless';
import { parse } from 'acorn';
import { simple }  from 'acorn-walk';
import { generate } from 'escodegen';
import tool from "@/utils/tool";

const props = defineProps({
  dataFormId: String,
  operate: String,
  name: String,
  initData: Object,
  disabled: {
    type: Boolean,
    default: false
  },
  dataList: {
    type: Array,
    default: () => []
  },
  defaultValue: {
    type: Object,
    default: () => {}
  },
  options: {
    type: Object,
    default: () => {}
  },
  inputParam: Object
});

const initializing = ref(true);
const loading = ref(false);
const fieldList = ref([]);
const tableEditRef = ref();
const dataForm = ref();
const formInfo = ref({});
const initData = ref({});
const formData = ref({});
const customFun = ref({});
const rules = ref({});
const tipsTopHtml = ref('');
const tipsBottomHtml = ref('');
const disabled = ref(false);
const userStore = useUserStore();
const page = inject('page', { getPageData: () => {} });
provide('formData', formData);

const loadData = async(formId) => {
  loading.value = true;
  try {
    await loadFormInfo(formId);
    await obtainTips(formInfo.value);
    await loadInitData(formInfo.value);
    await loadDataField(formInfo.value.modelId);
    initModelField();
    if (props.options && props.options.loading) {
      await loadModelData(props.options);
    } else if (props.operate === '新增'
        || props.operate === '编辑'
        || props.operate === '复制'
        || props.operate === '明细') {
      await initFormData();
    } else if (props.operate === '批量编辑') {
      initTableData();
    } else if (props.options) {
      await loadModelData(props.options);
    }
    initFormRules();
    loading.value = false;
  } finally {
    loading.value = false;
  }
};

const loadDataField = async(modelId) => {
  fieldList.value = await getDataFieldList(modelId) || [];
};

const loadFormInfo = async(formId) => {
  formInfo.value = await getDataForm(formId);
  if (formInfo.value.scriptContent) {
    try {
      parseScript(formInfo.value.scriptContent);
    } catch (e) {
      console.error('表单脚本内容错误！', e);
    }
  }
};

const parseScript = (code) => {
  simple(parse(code, null), {
    FunctionDeclaration(node) {
      const params = node.params.map(param => param.name);
      const isAsync = node.async;
      const bodyCode = generate(node.body);

      if (isAsync) {
        const asyncFunction = Object.getPrototypeOf(async function () {}).constructor;
        customFun.value[node.id.name] = new asyncFunction(...params, bodyCode);
      } else {
        customFun.value[node.id.name] = new Function(...params, bodyCode);
      }
    },
  });
};

const loadInitData = async(formInfo) => {
  if (formInfo.initScriptId) {
    let res = await api.runScript(formInfo.initScriptId);
    initData.value = res.data || {};
  }
};

const loadModelData = async(options) => {
  if (!options.pageCompId || !options.dataModelId) return;

  if (props.operate !== '新增') {
    if (Object.keys(props.inputParam || {}).length === 0) {
      formData.value = Object.assign({}, props.initData || {});
      return;
    }

    const params = {};
    Object.entries(props.inputParam || {}).forEach(function([key, value]) {
      const field = fieldList.value.find(item => item.id === key);
      if (field) {
        params[field.fieldName] = value;
      }
    });

    if (formInfo.value.batchEdit === '1') {
      const url = '/codeless/data-model/' + options.dataModelId + '/list';
      const res = await api.post(url, params);
      formData.value.list = res.data || [];
      for (const data of formData.value.list) {
        await customInitFormData(data);
      }
    } else {
      const url = '/codeless/data-model/' + options.dataModelId + '/data';
      const res = await api.post(url, params);
      formData.value = Object.assign({}, res.data || {});
      await customInitFormData(formData.value);
    }
  }
};

const loadDefaultData = async() => {
  let defaultData = {};
  for (let model of formInfo.value.modelList) {
    for (let field of model.fieldList) {
      defaultData[field.fieldName] = null;
      let res = await userStore.parseSymbol(field.defaultValue);
      if (res) {
        defaultData[field.fieldName] = res;
      }
    }
  }
  for (const key in defaultData) {
    if (defaultData[key] && !formData.value[key]) {
      formData.value[key] = defaultData[key];
    }
  }

  for (const key in props.inputParam) {
    const find = fieldList.value.find(field => field.id === key);
    if(find) {
      formData.value[find.fieldName] = props.inputParam[key];
    }
  }
}

const obtainTips = async(data) => {
  if (data.tipsOrigin === '输入') {
    if (data.tipsPosition === '顶部') {
      tipsTopHtml.value = data.tipsHtml || {};
    } else {
      tipsBottomHtml.value = data.tipsHtml || {};
    }
  }

  if (data.tipsOrigin === '脚本' && data.tipsScriptId) {
    let res = await api.runScript(data.tipsScriptId);
    if (data.tipsPosition === '顶部') {
      tipsTopHtml.value = res.data || '';
    } else {
      tipsBottomHtml.value = res.data || '';
    }
  }
};

const initModelField = () => {
  (formInfo.value.modelList || []).forEach(model => {
    (model.fieldList || []).forEach(field => {
      field.dataField = fieldList.value.find(item => item.id === field.fieldId) || {};
    });
  });
};

const initFormData = async() => {
  let pkName = getPKName(fieldList.value);
  if (pkName && !(props.initData && props.initData[pkName]) && !disabled.value) {
    let defaultData = {};
    for (let model of formInfo.value.modelList) {
      for (let field of model.fieldList) {
        defaultData[field.fieldName] = null;
        let res = await userStore.parseSymbol(field.defaultValue);
        if (res) {
          defaultData[field.fieldName] = res;
        }
      }
    }
    formData.value = Object.assign(
      {},
      defaultData,
      props.inputParam || {},
      filterValue(initData.value, pkName),
      filterValue(props.initData, pkName));
  } else {
    formData.value = Object.assign({}, filterValue(props.initData, pkName));
  }

  await customInitFormData(formData.value);

  await nextTick(() => {
    dataForm.value && dataForm.value.clearValidate();
  });
};

const customInitFormData = async(formData) => {
  if (customFun.value && customFun.value['initData']) {
    await customFun.value['initData'](formData, context);
  }
};

const initTableData = () => {
  formData.value.list = tool.deepClone(props.dataList) || [];
  if (customFun.value && customFun.value['initData']) {
    const result = customFun.value['initData'](formData.value.list, context);
    if (result instanceof Promise) {
      result.then(res => {
        formData.value.list = res;
      });
    } else if (result) {
      formData.value.list = result;
    }
  }
};

const filterValue = (data, pkName) => {
  let result = {};
  if (pkName && data && data[pkName]) {
    result[pkName] = data[pkName];
  }
  for (let model of formInfo.value.modelList) {
    for (let field of model.fieldList) {
      if (data && (data[field.fieldName] || data[field.fieldName] === 0)) {
        result[field.fieldName] = data[field.fieldName];
        if (data[field.fieldName + '_name']) {
          result[field.fieldName + '_name'] = data[field.fieldName + '_name'];
        }
      }
    }
  }
  return result;
}

const initFormRules = () => {
  rules.value = {};
  (formInfo.value.modelList || []).forEach(model => {
    (model.fieldList || []).forEach(field => {
      let ruleList = [];
      if (field.required === '1') {
        ruleList.push({
          required: true,
          message: "【" + (field.aliasName || field.fieldName) + "】为必填项",
          trigger: ['blur', 'change']
        });
      }
      let dataField = fieldList.value.find(item => item.id === field.fieldId);
      if (dataField) {
        switch (dataField.fieldType) {
          case '字符':
            ruleList.push({
              max: dataField.fieldSize || 1000,
              message: "【" + (field.aliasName || dataField.fieldName) + "】" +
                "长度必须小于等于 " + (dataField.fieldSize || 1000) + " 个字符",
              trigger: ['blur', 'change']
            });
            break;
          case '整数':
          case '小数':
            ruleList.push({
              min: dataField.minValue,
              max: dataField.maxValue || 2147483647,
              minEqu: dataField.containMinValue === '1',
              maxEqu: dataField.containMaxValue === '1',
              message: "【" + (dataField.aliasName || dataField.fieldName) + "】" +
                "必须大于" + (dataField.containMinValue === '1' ? '等于' : '') + ' ' +
                (dataField.minValue || 0) +
                " 小于" + (dataField.containMaxValue === '1' ? '等于' : '') + ' ' +
                (dataField.maxValue || 2147483647),
              trigger: ['blur', 'change'],
              validator: (rule, value, callback) => {
                if (value !== 0 && !value) {
                  callback();
                } else if (rule.min && rule.minEqu && value < rule.min) {
                  callback(new Error(rule.message));
                } else if (rule.min && !rule.minEqu && value <= rule.min) {
                  callback(new Error(rule.message));
                } else if (rule.maxEqu && value > rule.max) {
                  callback(new Error(rule.message));
                } else if (!rule.maxEqu && value >= rule.max) {
                  callback(new Error(rule.message));
                } else {
                  callback();
                }
              }
            });
            break;
        }
      }
      ruleList.push({
        validator: (rule, value, callback) => validateField(field, callback),
        trigger: ['blur', 'change']
      });
      rules.value[dataField.fieldName] = ruleList;
    });
  });
};

const getPKName = (list) => {
  if (!list) return null;
  for (let field of list) {
    if (field.primaryKey === '1') {
      return field.fieldName;
    }
  }
  return null;
};

const validateField = (field, callback) => {
  if (customFun.value && customFun.value['validate_' + field.fieldName]) {
    customFun.value['validate_' + field.fieldName](formData.value, callback, context);
  } else {
    callback();
  }
};

const validate = (callback) => {
  const promise = new Promise((resolve, reject) => {
    if (formInfo.value.batchEdit === '1') {
      tableEditRef.value && tableEditRef.value
        .validate(valid => resolve(valid))
        .catch(e => {
          console.log('表格检验失败！', e);
          reject(e);
        });
    } else {
      if (!dataForm.value) {
        resolve(true);
        return;
      }

      dataForm.value.validate(valid => {
        if (!valid) {
          console.log('表单校验失败！')
          reject(valid);
          return;
        }

        if (!(customFun.value && customFun.value.validate)) {
          resolve(valid);
          return;
        }

        let result = customFun.value.validate(formData.value, context);
        if (typeof result !== 'undefined' && result instanceof Error) {
          ElMessage.error(result.message);
        } else if (result instanceof Promise) {
          result.then(res => {
            if (typeof res !== 'undefined' && res instanceof Error) {
              ElMessage.error(res.message);
            } else {
              resolve(valid);
            }
          });
        } else {
          resolve(valid);
        }
      }).catch(e => {
        console.log('表单校验失败！', e);
        reject(e);
      });
    }
  });
  promise
    .then(valid => callback(valid))
    .catch(e => {
      console.log('表单校验异常！', e);
      callback(false);
    });
  return promise;
};

const style = ref({'无': 'none', '实线': 'solid', '虚线': 'dashed', '标签': 'label'});
const getStyle = (key) => {
  return style.value[key] || 'solid';
};

const position = ref({'居左': 'left', '居中': 'center', '居右': 'right'});
const getPosition = (key) => {
  return position.value[key] || 'left';
};

const getData = () => {
  if (formInfo.value.batchEdit === '1') {
    return tableEditRef.value?.getData();
  } else {
    return formData.value;
  }
};

const init = async() => {
  if (!props.dataFormId) {
    return;
  }
  initializing.value = true;
  disabled.value = (props.operate === '新增' || props.operate === '编辑' ) ? false : (props.disabled || false);
  await loadData(props.dataFormId);
  await loadDefaultData();
  await nextTick(() => initializing.value = false);
};

watch(() => tool.deepClone(formData.value), (newVal, oldValue) => {
  if (disabled.value || initializing.value) return;
  if (formInfo.value.batchEdit === '1') {
    if (!newVal.list || newVal.list.length === 0 || newVal.list.length !== oldValue.list) return;

    for (let i=0;i<newVal.list.length;i++) {
      changeValue(newVal.list[i], oldValue.list[i]);
    }
  } else {
    changeValue(newVal, oldValue);
  }
}, {deep: true});

const changeValue = (newVal, oldValue) => {
  for (const prop in newVal) {
    if ((newVal[prop] || '') !== oldValue[prop]) {
      if (customFun.value && customFun.value['change_' + prop]) {
        updateValue(customFun.value['change_' + prop](tool.deepClone(newVal), context));
      }
    }
  }

  if (customFun.value && customFun.value.change) {
    updateValue(customFun.value.change(tool.deepClone(newVal), context));
  }
};

const updateValue = (result) => {
  if (result instanceof Error) {
    ElMessage.error(result.message);
  } else if (result instanceof Promise) {
    result.then(data => {
      for (const p in data || {}) {
        formData.value[p] = data[p];
      }
    });
  } else {
    for (const p in result || {}) {
      formData.value[p] = result[p];
    }
  }
};

const context = {
  getTool: function() {
    return tool;
  },

  getPageData: function () {
    return page?.getPageData();
  },

  getUser: function() {
    return userStore.getUserData();
  },

  getOrg: function() {
    return userStore.getOrgData();
  },

  runScript: function(id, params) {
    return api.runScript(id, params);
  }
};

onMounted(() => {
  init();
});

defineExpose({ validate, getData });
</script>

<style scoped>
</style>